﻿using System;
using System.Collections.Generic;
using System.Linq;
using BitButterCORE.Common;
using BitButterCORE.Object;

namespace BitButterCORE.Factory
{
	public abstract class BaseFactory<TObject, TFactory> : BaseSingleton<TFactory>, IFactory
		where TObject : BaseObject
	{
		public virtual TObject this[uint id]
		{
			get
			{
				return Factory.ContainsKey(id) ? Factory[id] : null;
			}
		}

		protected virtual Predicate<TObject> SceneFilter => (obj) => true;

		#region IFactory Members

		BaseObject IFactory.this[uint id] => this[id];

		IEnumerable<BaseObject> IFactory.Objects => Objects.Where(obj => SceneFilter(obj));

		void IFactory.AddObject(BaseObject obj)
		{
			if (obj is TObject tObj)
			{
				AddObject(tObj);
			}
		}

		public bool HasChanges => hasChanges;
		bool hasChanges;

		public void MarkHasChanges()
		{
			hasChanges = true;
		}

		public void ClearChanges()
		{
			hasChanges = false;
		}

		#endregion

		public IEnumerable<TObject> Objects
		{
			get
			{
				return Factory.Values;
			}
		}

		protected IEnumerable<TObject> QueryWithNoCaching(Predicate<TObject> predicate)
		{
			return Query(Objects, predicate, null);
		}

		protected IEnumerable<TObject> QueryWithNoCaching(IEnumerable<TObject> source, Predicate<TObject> predicate)
		{
			return Query(source, predicate, null);
		}

		protected IEnumerable<TObject> Query(Predicate<TObject> predicate, List<TObject> cache, bool enableCaching = false)
		{
			return Query(Objects, predicate, cache, enableCaching);
		}

		protected IEnumerable<TObject> Query(IEnumerable<TObject> source, Predicate<TObject> predicate, List<TObject> cache, bool enableCaching = false)
		{
			if (enableCaching && cache == null)
			{
				cache = new List<TObject>();
			}

			if (enableCaching && cache.Count > 0)
			{
				foreach (TObject obj in cache)
				{
					yield return obj;
				}
			}
			else
			{
				foreach (TObject obj in source)
				{
					if (predicate(obj))
					{
						if (obj != null)
						{
							if (enableCaching)
							{
								cache.Add(obj);
							}
							yield return obj;
						}
					}
				}
			}
		}

		public uint AddObject(TObject obj)
		{
			if (!obj.IsManaged)
			{
				obj.ID = NewID;
				Factory.Add(obj.ID, obj);
				hasChanges = true;
				return obj.ID;
			}
			return invalidID;
		}

		public uint AddLoadedObject(TObject obj)
		{
			if (obj.IsManaged && !Factory.ContainsKey(obj.ID))
			{
				Factory.Add(obj.ID, obj);
				hasChanges = true;
				return obj.ID;
			}
			return invalidID;
		}

		public virtual void RemoveObject(TObject obj)
		{
			if (obj.IsManaged && Factory.ContainsKey(obj.ID))
			{
				Factory.Remove(obj.ID);
				obj.ID = invalidID;
				hasChanges = true;
			}
		}

		public virtual void ClearFactory()
		{
			foreach (var obj in Factory.Values)
			{
				obj.ID = invalidID;
			}
			Factory.Clear();
			hasChanges = true;
		}

		uint NewID => currentID++;
		uint currentID = 1;
		readonly uint invalidID = 0;

		protected Dictionary<uint, TObject> Factory
		{
			get
			{
				if (factory == null)
				{
					factory = new Dictionary<uint, TObject>();
				}
				return factory;
			}
		}

		Dictionary<uint, TObject> factory;

		public virtual byte[] Serialize()
		{
			return null;
		}

		public virtual void Deserialize(byte[] factoryPayload)
		{
		}
	}
}
